package com.nf.db;

import com.nf.db.handlers.ResultSetHandler;
import com.nf.db.util.CleanerUtils;

import javax.sql.DataSource;
import java.sql.*;

/*
 *   此类是用来处理sql的增删查改
 * */
public class SqlExecutor {
    private DataSource dataSource;

    public SqlExecutor() {
    }

    public SqlExecutor(DataSource dataSource) {
        this.dataSource = dataSource;  // 设置数据源
    }

    /*
     * 此方法是用来处理增删改的功能
     * */
    public int update(Connection conn, String sql, Object... obj) {
        int rows = 0;  // 初始化受影响行数
        PreparedStatement stmt = null;  // 初始化PreparedStatement
        try {
            stmt = conn.prepareStatement(sql);  // 使用连接创建PreparedStatement
            fillStatement(stmt, obj);  // 调用填充语句参数的方法
            rows = stmt.executeUpdate();  // 执行更新操作，获取受影响行数
        } catch (SQLException e) {
            throw new DaoException("数据操作失败", e);  // 抛出数据操作失败的异常
        } finally {
            CleanerUtils.closeQuietly(stmt, conn);  // 最终关闭资源
        }
        return rows;  // 返回受影响行数
    }

    /*
     * 此方法是update方法的重载方法，他的作用是不用传递连接，直接从数据源获取连接，然后调用update方法
     * */
    public int update(String sql, Object... obj) {
        Connection connection = null;  // 初始化数据库连接
        try {
            connection = getConnection();  // 获取数据源连接
        } catch (RuntimeException e) {
            throw new DaoException("数据库连接失败", e);  // 抛出数据库连接失败的异常
        }
        return update(connection, sql, obj);  // 调用重载方法，并返回结果
    }

    /*
     * 此方法是insert方法的重载方法，他的作用是不用传递连接，直接从数据源获取连接，然后调用insert方法
     * */
    public <T> T insert(String sql, ResultSetHandler<T> handler, Object... obj) {
        Connection conn = null;  // 初始化数据库连接
        conn = getConnection();  // 获取数据源连接
        return insert(conn, sql, handler, obj);  // 调用方法，并返回结果
    }

    /*
     * 使用此方法可以获得自增长的主键值
     * */
    public <T> T insert(Connection conn, String sql, ResultSetHandler<T> handler, Object... params) {
        T keys;
        PreparedStatement stmt = null;
        try {
            stmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);// 使用连接创建PreparedStatement
            fillStatement(stmt, params);
            stmt.executeUpdate();
            ResultSet generatedKeys = stmt.getGeneratedKeys(); // 获取生成的主键
            keys = handler.handle(generatedKeys); // 处理生成的主键
        } catch (SQLException e) {
            throw new DaoException("数据操作失败", e);  // 抛出数据操作失败的异常
        } finally {
            CleanerUtils.closeQuietly(stmt, conn);  // 最终关闭资源
        }
        return keys;  // 返回受影响参数
    }

    public <T> T query(Connection conn, String sql, ResultSetHandler<T> handler, Object... obj) {
        //创建PreparedStatement，来接受查询结果
        PreparedStatement stmt = null;
        //创建ResultSet，来处理查询结果
        ResultSet rs = null;
        //创建泛型的结果对象，来存放这个处理完的数据
        T result = null;

        try {
            //创建PreparedStatement
            stmt = conn.prepareStatement(sql);
            //判断有没有传参数，如果有，就填充参数
            if (obj != null) {
                //填充参数
                fillStatement(stmt, obj);
            }
            //执行查询，并获取结果集
            rs = stmt.executeQuery();
            //处理结果集，并获取返回结果
            result = handler.handle(rs);
        } catch (SQLException e) {
            //如果出现异常，则会在控制台抛出查询数据库失败的异常
            throw new DaoException("查询数据库失败", e);
        } finally {
            //无论这个方法是否抛出异常，都会关闭资源
            CleanerUtils.closeQuietly(rs, stmt, conn);
        }
        return result;
    }

    private static void fillStatement(PreparedStatement stmt, Object... obj) {
        //处理sql语句的参数
        for (int i = 0; i < obj.length; i++) {
            try {
                //设置参数
                stmt.setObject(i + 1, obj[i]);
            } catch (SQLException e) {
                throw new DaoException("设置参数失败", e);
            }
        }
    }

    /*
     * 这里的修饰符用protected(受保护的)，外界访问不到，只能在这个类和包里面访问，不能在其他类里面使用
     * 意味着这个方法只能在SqlExecutor类内部使用，客户如果还有其他需求，可以继承这个类，重写这个方法,以实现客户需要的功能
     * */
    protected Connection getConnection() {
        try {
            //从数据源获取连接
            return dataSource.getConnection();
        } catch (SQLException e) {
            throw new DaoException("连接有问题", e);
        }
    }

    //这个用protected(受保护的)修饰符，其实是和getConnection()方法是一样的，为了方便让子类重写，以实现自己的需求
    protected DataSource getDataSource() {
        //返回本类的数据源
        return this.dataSource;
    }
}
